package com.softwarecraftsmen.orogeny;

import com.softwarecraftsmen.orogeny.actions.ModuleAction;
import com.softwarecraftsmen.orogeny.filing.AbsoluteDirectory;
import com.softwarecraftsmen.orogeny.filing.AbsoluteFile;
import com.softwarecraftsmen.orogeny.filing.AbsolutePath;
import com.softwarecraftsmen.orogeny.filing.AbsolutePaths;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import static com.softwarecraftsmen.orogeny.actions.RegisterAction.register;
import static com.softwarecraftsmen.orogeny.filing.AbsoluteDirectoriesAndFiles.absoluteDirectoriesAndFiles;
import static com.softwarecraftsmen.orogeny.filing.AbsoluteFiles.EmptyAbsoluteFiles;
import static com.softwarecraftsmen.orogeny.filing.AbsoluteFiles.absoluteFiles;
import static java.lang.System.arraycopy;

/**
 * This file is Copyright © 2009 Vubble Limited. All Rights Reserved.
 */
@SuppressWarnings({"UnusedDeclaration"})
public abstract class ConvenientBuildScript extends BuildScript
{
	private String[] outputSubFolders = null;
	private String[] librarySubFolders = null;
	private String[] sourceSubFolders = null;
	private String[] packageTemplateSubFolders = null;
	private final Set<String> compiledTaskNamesKnown = new LinkedHashSet<>();

    protected void outputSubfolders(final @NotNull String... outputSubFoldersBelowBuildDirectory)
	{
		outputSubFolders = outputSubFoldersBelowBuildDirectory;
	}

	protected void librarySubfolders(final @NotNull String... librarySubFoldersBelowBuildDirectory)
	{
		librarySubFolders = librarySubFoldersBelowBuildDirectory;
	}

	protected void sourceSubfolders(final @NotNull String... sourceSubFoldersBelowBuildDirectory)
	{
		sourceSubFolders = sourceSubFoldersBelowBuildDirectory;
	}

	protected void packageTemplateSubFolders(final @NotNull String... subFolderNames)
	{
		packageTemplateSubFolders = subFolderNames;
	}

	@NotNull
	protected AbsoluteDirectory library(final @NotNull String... subFolderNames)
	{
		return inBuildDirectory(librarySubFolders).subDirectory(subFolderNames);
	}

	@NotNull
	protected AbsoluteDirectory source(final @NotNull String... subFolderNames)
	{
		return inBuildDirectory(sourceSubFolders).subDirectory(subFolderNames);
	}

	@NotNull
	protected AbsoluteDirectory output(final @NotNull String... subFolderNames)
	{
		return inBuildDirectory(outputSubFolders).subDirectory(subFolderNames);
	}

	@NotNull
	protected AbsoluteDirectory packageTemplates(final @NotNull String... subFolderNames)
	{
		return inBuildDirectory(packageTemplateSubFolders).subDirectory(subFolderNames);
	}

	@NotNull
	protected CompileTask compileTask(final @NotNull String moduleName, final @NotNull String... folders)
	{
		compiledTaskNamesKnown.add("compile " + moduleName);
		return new CompileTask(moduleName, folders);
	}

	protected void compile()
	{
		final Task task = task("compile");
		final String[] names = compiledTaskNamesKnown.toArray(new String[compiledTaskNamesKnown.size()]);
		compiledTaskNamesKnown.clear();
		task.dependsOn(names);
	}

	public final class CompileTask
	{
		private final String moduleName;
		private final String[] folders;

		public CompileTask(final @NotNull String moduleName, final @NotNull String... folders)
		{
			this.moduleName = moduleName;
			this.folders = folders;
		}

		@NotNull
		public DependsOnBeingCompiled dependsOnBeingCompiled(final @NotNull String... moduleNames)
		{
			return new DependsOnBeingCompiled(moduleNames);
		}

		@NotNull
		public DependsOnBeingCompiled.DependsOn dependsOn(final @NotNull String... taskNames)
		{
			return new DependsOnBeingCompiled().dependsOn(taskNames);
		}

		public final class DependsOnBeingCompiled
		{
			private final String[] moduleNames;

			public DependsOnBeingCompiled(final @NotNull String... moduleNames)
			{
				this.moduleNames = moduleNames;
			}

			@NotNull
			public DependsOn dependsOn(final @NotNull String... taskNames)
			{
				return new DependsOn(taskNames);
			}

			@NotNull
			public DependsOn.UsingClassPath usingLibraries()
			{
				return new DependsOn().usingLibraries(EmptyAbsoluteFiles);
			}

			@NotNull
			public DependsOn.UsingClassPath usingLibraries(final @NotNull AbsolutePath... classPath)
			{
				return usingLibraries(absoluteDirectoriesAndFiles(classPath));
			}

			@NotNull
			public DependsOn.UsingClassPath usingLibraries(final @NotNull AbsolutePaths... classPath)
			{
				return new DependsOn().usingLibraries(absoluteDirectoriesAndFiles(classPath));
			}

			public final class DependsOn
			{
				private final String[] taskNames;

				public DependsOn(final @NotNull String... taskNames)
				{
					this.taskNames = taskNames;
				}

				@NotNull
				public UsingClassPath usingLibraries()
				{
					return new UsingClassPath(EmptyAbsoluteFiles);
				}

				@NotNull
				public UsingClassPath usingLibraries(final @NotNull AbsolutePath... classPath)
				{
					return usingLibraries(absoluteDirectoriesAndFiles(classPath));
				}

				@NotNull
				public UsingClassPath usingLibraries(final @NotNull AbsolutePaths... classPath)
				{
					return new UsingClassPath(absoluteDirectoriesAndFiles(classPath));
				}

				public final class UsingClassPath
				{
					private final AbsolutePaths classPath;

					public UsingClassPath(final @NotNull AbsolutePaths classPath)
					{
						this.classPath = registeredPaths(moduleNames).and(classPath);
					}

					public void withoutMainClass()
					{
						final String[] allTaskNames = new String[taskNames.length + moduleNames.length];
						arraycopy(taskNames, 0, allTaskNames, 0, taskNames.length);
						for (int index = 0; index < moduleNames.length; index++)
						{
							allTaskNames[index + taskNames.length] = "compile " + moduleNames[index];
						}

						task("compile " + moduleName).dependsOn(allTaskNames).does
						(
							module(moduleName, folders).usingClassPath(classPath).withoutMainClass(),
							register(moduleName + ".source.zip").asPaths(sourcePaths())
						);
					}

					public void withMainClass(final @NotNull String mainClassName)
					{
						final String[] allTaskNames = new String[taskNames.length + moduleNames.length];
						arraycopy(taskNames, 0, allTaskNames, 0, taskNames.length);
						for (int index = 0; index < moduleNames.length; index++)
						{
							allTaskNames[index + taskNames.length] = "compile " + moduleNames[index];
						}

						task("compile " + moduleName).dependsOn(allTaskNames).does
						(
							module(moduleName, folders).usingClassPath(classPath).withMainClass(mainClassName),
							register(moduleName + ".source.zip").asPaths(sourcePaths())
						);
					}

					private AbsolutePaths[] sourcePaths()
					{
						final List<AbsolutePaths> dependentSourcePathsAndOurSourcePath = new ArrayList<>();
						for (String dependentModuleName : moduleNames)
						{
							final String sourcePathZipName = dependentModuleName + ".source.zip";
							dependentSourcePathsAndOurSourcePath.add(registeredPaths(sourcePathZipName));
						}
						final AbsoluteFile sourceZipFile = output(moduleName).file(moduleName + ".source.zip");
						dependentSourcePathsAndOurSourcePath.add(absoluteFiles(sourceZipFile));
						return dependentSourcePathsAndOurSourcePath.toArray(new AbsolutePaths[dependentSourcePathsAndOurSourcePath.size()]);
					}
				}
			}
		}
	}

	@NotNull
	protected SpecifySourceAdapter module(final @NotNull String name, final @NotNull String... folders)
	{
		return new SpecifySourceAdapter(ModuleAction.module(name).compile(source(folders)), name);
	}

	public final class SpecifySourceAdapter
	{
		private final ModuleAction.SpecifyName.SpecifySource specifySource;
		private final String name;

		public SpecifySourceAdapter(final @NotNull ModuleAction.SpecifyName.SpecifySource specifySource, final @NotNull String name)
		{
			this.specifySource = specifySource;
			this.name = name;
		}

		@NotNull
		public ModuleAction.SpecifyName.SpecifySource.SpecifyClassPath.SpecifyOutputDirectory usingClassPath()
		{
			return specifySource.usingClassPath().toOutputDirectory(output());
		}

		@NotNull
		public ModuleAction.SpecifyName.SpecifySource.SpecifyClassPath.SpecifyOutputDirectory usingClassPath(final @NotNull AbsolutePath... classPath)
		{
			return specifySource.usingClassPath(classPath).toOutputDirectory(output());
		}

		@NotNull
		public ModuleAction.SpecifyName.SpecifySource.SpecifyClassPath.SpecifyOutputDirectory usingClassPath(final @NotNull AbsolutePaths... classPath)
		{
			return specifySource.usingClassPath(classPath).toOutputDirectory(output());
		}
	}
}
